﻿using System;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Web;
using System.Xml;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using System.Text;
using System.Text.RegularExpressions;
using System.Reflection;
using System.Globalization;
using System.ComponentModel;
using System.Collections;
using System.Collections.Generic;

using Microsoft.SqlServer.Server;

namespace PickGold.SqlServer
{
	/// <summary>
	/// Common
	/// </summary>
	public static class Net
	{
		private static UdpClient Udp;
		private static string Host = "127.0.0.1";
		private static int Port = 52866;

		private static int InnerSendUdpData(byte[] data, string host, int port)
		{
			if (string.IsNullOrEmpty(host))
				host = Host;
			else
				Host = host;
			if (port == 0)
				port = Port;
			else
				Port = port;
			if (data == null || data.Length == 0)
				return 0;

			//var socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
			//var ips = Dns.GetHostAddresses(host);
			//for (var i = 0; i < ips.Length; i++)
			//{
			//    if (ips[i].AddressFamily == socket.AddressFamily)
			//    {
			//        if (ips[i] == IPAddress.Broadcast)
			//            socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, 1);
			//        i = socket.SendTo(data, 0, data.Length, SocketFlags.None, new IPEndPoint(ips[i], port));
			//        socket.Close();
			//        return i;
			//    }
			//}
			//return -1;

			var udp = Udp;
			if (udp == null)
				udp = new UdpClient();
			var i = data.Length;
			try
			{
				if (Math.Abs(port) > ushort.MaxValue || host.IndexOfAny(Common.SQL_COMMAND_FLAG) >= 0)
				{
					using (var con = Common.OpenConnection(null))
					{
						using (var cmd = con.CreateCommand())
						{
							cmd.CommandText = host;
							if (host.IndexOfAny(Common.SQL_COMMAND_FLAG) < 0)
								cmd.CommandType = CommandType.StoredProcedure;
							cmd.Parameters.Add(new SqlParameter("@data", SqlDbType.VarBinary, -1)).Value = data;
							cmd.Parameters.Add(new SqlParameter("@host", SqlDbType.NVarChar, -1)).Value = host;
							cmd.Parameters.Add(new SqlParameter("@port", SqlDbType.Int)).Value = port;
							i = cmd.ExecuteNonQuery();
						}
						Common.CloseConnection(con, null);
					}
				}
				else
				{
					//SqlContext.Pipe.Send("Will send data length:" + i);
					if (port > 0)
						i = udp.Send(data, i, host, port);
					else
						udp.BeginSend(data, i, host, port * -1, EndSend, udp);

					//SqlContext.Pipe.Send("Fact sent data length:" + i);
					//SqlContext.Pipe.Send("Data from [" + udp.Client.LocalEndPoint + "] to [" + host + ":" + port + "].");
				}
			}
			catch (Exception x)
			{
				if (SqlContext.IsAvailable && SqlContext.Pipe != null)
					SqlContext.Pipe.Send(x.ToString());
				i *= -1;
			}
			if (Udp == null)
			{
				ServicePointManager.DefaultConnectionLimit = short.MaxValue;
				ServicePointManager.Expect100Continue = false;
				try
				{
					udp.BeginReceive(EndReceive, udp);
				}
				catch { }
				Udp = udp;
			}
			return i;
		}

		private static void EndSend(IAsyncResult result)
		{
			var udp = result.AsyncState as UdpClient;
			try
			{
				udp.EndSend(result);
			}
			catch { }
		}

		private static void EndReceive(IAsyncResult result)
		{
			var udp = result.AsyncState as UdpClient;
			try
			{
				var p = default(IPEndPoint);
				var d = udp.EndReceive(result, ref p);
				if (d != null && d.Length > 8)//数据包的前八字节必须是时间，自1970-1-1的毫秒数，是包发起时间，发起时间必须距现在一分钟内，以防止DDOS攻击。
				{
					var n = BitConverter.ToInt64(d, 0) + Other.JS_1970;
					n = n * TimeSpan.TicksPerMillisecond;
					n = DateTime.Now.Ticks - n;
					if (n > 0 && n < TimeSpan.TicksPerMinute)
					{
						Common.ItemData.Set(p.ToString(), d, TimeSpan.TicksPerMinute, 0L);
						udp.BeginSend(d, d.Length, Host, Math.Abs(Port), EndSend, udp);
						d = new byte[32];
						BitConverter.GetBytes(p.Port).CopyTo(d, 0);
						p.Address.GetAddressBytes().CopyTo(d, sizeof(int));
						udp.BeginSend(d, d.Length, Host, Math.Abs(Port), EndSend, udp);
					}
					else
					{
						Common.ItemData.Set(p.ToString(), Encoding.Default.GetBytes("数据包的前八字节必须是时间，自1970-1-1的毫秒数，是包发起时间，发起时间必须距现在一分钟内，以防止DDOS攻击。"), TimeSpan.TicksPerMinute, 0L);
					}
				}
			}
			catch { }
			try
			{
				udp.BeginReceive(EndReceive, udp);
			}
			catch { }
		}

		/// <summary/>
		[SqlProcedure]
		[Description("发送UDP数据包")]
		[return: Description("如果成功，返回发送字节数，失败返回负数")]
		public static SqlInt32 SendUdpText([Description("要发送的数据")] SqlString data, [Description("远程主机名或IP地址，如果包含括号或等号则表示为数据命令")] SqlString host, [Description("远程端口，正表示同步发送，负表示异步发送，异步发送无法得知发送是否成功，为零或默认 52866")] SqlInt32 port)
		{
			return Net.InnerSendUdpData(data.IsNull || string.IsNullOrEmpty(data.Value) ? null : Encoding.UTF8.GetBytes(data.Value), host.IsNull ? Host : host.Value, port.IsNull ? Port : port.Value);
		}

		/// <summary/>
		[SqlProcedure]
		[Description("发送UDP数据包")]
		[return: Description("如果成功，返回发送字节数，失败返回负数")]
		public static SqlInt32 SendUdpData([Description("要发送的数据")] SqlBinary data, [Description("远程主机名或IP地址，如果包含括号或等号则表示为数据命令")] SqlString host, [Description("远程端口，正表示同步发送，负表示异步发送，异步发送无法得知发送是否成功，为零或默认 52866")] SqlInt32 port)
		{
			return Net.InnerSendUdpData(data.IsNull || data.Length == 0 ? null : data.Value, host.IsNull ? Host : host.Value, port.IsNull ? Port : port.Value);
		}

		/// <summary/>
		[SqlProcedure]
		[Description("发送UDP数据包")]
		[return: Description("如果成功，返回发送字节数，失败返回负数")]
		public static SqlInt32 SendUdpXml([Description("要发送的数据")] SqlXml data, [Description("远程主机名或IP地址，如果包含括号或等号则表示为数据命令")] SqlString host, [Description("远程端口，正表示同步发送，负表示异步发送，异步发送无法得知发送是否成功，为零或默认 52866")] SqlInt32 port)
		{
			return Net.InnerSendUdpData(data.IsNull || string.IsNullOrEmpty(data.Value) ? null : Encoding.UTF8.GetBytes(data.Value), host.IsNull ? Host : host.Value, port.IsNull ? Port : port.Value);
		}

		/// <summary>
		/// DownloadString
		/// </summary>
		/// <param name="url">url</param>
		/// <param name="headers">标准HTTP头</param>
		/// <param name="sync">大于零为异步尝试，结果存储在ItemValue中，返回值为键</param>
		/// <returns>url result</returns>
		[SqlFunction(IsDeterministic = false, IsPrecise = false)]
		public static SqlString DownloadString([Description("资源的HTTP地址")] SqlString url, [Description("标准HTTP头")] SqlString headers, [Description("大于零为异步尝试，结果存储在ItemValue中，返回值为键")] SqlInt32 sync)
		{
			if (url.IsNull || string.IsNullOrEmpty(url.Value))
				return null;

			if (sync.IsNull || sync.Value <= 0)
			{
				var wc = new WebClient();
				var result = FillHeaders(wc, headers.IsNull ? null : headers.Value).DownloadString(url.Value);
				wc.Dispose();
				return new SqlString(result);
			}

			var t = Guid.NewGuid().ToString("N");
			DownloadStringCompleted(new object[] { t, url.Value, headers.IsNull ? null : headers.Value, sync.Value, }, null);
			return t;
		}

		private static void DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
		{
			var os = sender as object[];
			try
			{
				var w = SyncCompleted(sender, e, e != null && e.Error == null && e.Result != null ? Encoding.Default.GetBytes(e.Result) : null, ref os);
				if (w == null)
					return;

				w.DownloadStringCompleted += DownloadStringCompleted;
				w.DownloadStringAsync(new Uri(w.BaseAddress), os);
			}
			catch (Exception x)
			{
				try
				{
					if (os != null && os.Length > 0)
						Common.ItemData.Set(os[0] as string, Encoding.Default.GetBytes(os[0] + x.Message), validity: 0L, timeout: DateTime.Now.AddHours(1).Ticks);
					SendUdpText("Sync:\r\n" + os[0] + "\r\n" + os[1], Host, Port);
				}
				catch { }
			}
			sender = null;
		}

		/// <summary>
		/// DownloadData
		/// </summary>
		/// <param name="url">url</param>
		/// <param name="headers">标准HTTP头</param>
		/// <param name="sync">大于零为异步尝试，结果存储在ItemValue中，返回值为键</param>
		/// <returns>url result</returns>
		[SqlFunction(IsDeterministic = false, IsPrecise = false)]
		public static SqlBinary DownloadData([Description("资源的HTTP地址")] SqlString url, [Description("标准HTTP头")] SqlString headers, [Description("大于零为异步尝试，结果存储在ItemValue中，返回值为键")] SqlInt32 sync)
		{
			if (url.IsNull || string.IsNullOrEmpty(url.Value))
				return null;

			if (sync.IsNull || sync.Value <= 0)
			{
				var wc = new WebClient();
				var result = FillHeaders(wc, headers.IsNull ? null : headers.Value).DownloadData(url.Value);
				wc.Dispose();
				return new SqlBinary(result);
			}

			var t = Guid.NewGuid().ToString("N");
			DownloadDataCompleted(new object[] { t, new Uri(url.Value), headers.IsNull ? null : headers.Value, sync.Value, }, null);
			return Encoding.Default.GetBytes(t);
		}

		private static void DownloadDataCompleted(object sender, DownloadDataCompletedEventArgs e)
		{
			var os = sender as object[];
			try
			{
				var w = SyncCompleted(sender, e, e != null && e.Error == null && e.Result != null ? e.Result : null, ref os);
				if (w == null)
					return;

				w.DownloadDataCompleted += DownloadDataCompleted;
				w.DownloadDataAsync(new Uri(w.BaseAddress), os);
			}
			catch (Exception x)
			{
				try
				{
					if (os != null && os.Length > 0)
						Common.ItemData.Set(os[0] as string, Encoding.Default.GetBytes(os[0] + x.Message), validity: 0L, timeout: DateTime.Now.AddHours(1).Ticks);
					SendUdpText("Sync:\r\n" + os[0] + "\r\n" + os[1], Host, Port);
				}
				catch { }
			}
			sender = null;
		}
		
		/// <summary>
		/// UploadString
		/// </summary>
		/// <param name="url">url</param>
		/// <param name="headers">标准HTTP头</param>
		/// <param name="data">上传数据</param>
		/// <param name="sync">大于零为异步尝试，结果存储在ItemValue中，返回值为键</param>
		/// <returns>url result</returns>
		[SqlFunction(IsDeterministic = false, IsPrecise = false)]
		public static SqlString UploadString([Description("资源的HTTP地址")] SqlString url, [Description("标准HTTP头")] SqlString headers, [Description("上传数据")] SqlString data, [Description("大于零为异步尝试，结果存储在ItemValue中，返回值为键")] SqlInt32 sync)
		{
			if (url.IsNull || string.IsNullOrEmpty(url.Value))
				return null;

			if (data.IsNull || string.IsNullOrEmpty(data.Value))
				return null;

			if (sync.IsNull || sync.Value <= 0)
			{
				var wc = new WebClient();
				var result = FillHeaders(wc, headers.IsNull ? null : headers.Value).UploadString(url.Value, WebRequestMethods.Http.Post, data.Value);
				wc.Dispose();
				return new SqlString(result);
			}

			var t = Guid.NewGuid().ToString("N");
			UploadStringCompleted(new object[] { t, url.Value, headers.IsNull ? null : headers.Value, data.Value, sync.Value, }, null);
			return t;
		}

		private static void UploadStringCompleted(object sender, UploadStringCompletedEventArgs e)
		{
			var os = sender as object[];
			try
			{
				var w = SyncCompleted(sender, e, e != null && e.Error == null && e.Result != null ? Encoding.Default.GetBytes(e.Result) : null, ref os);
				if (w == null)
					return;

				w.UploadStringCompleted += UploadStringCompleted;
				w.UploadStringAsync(new Uri(w.BaseAddress), WebRequestMethods.Http.Post, os[3] as string, os);
			}
			catch (Exception x)
			{
				try
				{
					if (os != null && os.Length > 0)
						Common.ItemData.Set(os[0] as string, Encoding.Default.GetBytes(os[0] + x.Message), validity: 0L, timeout: DateTime.Now.AddHours(1).Ticks);
					SendUdpText("Sync:\r\n" + os[0] + "\r\n" + os[1], Host, Port);
				}
				catch { }
			}
			sender = null;
		}

		/// <summary>
		/// UploadData
		/// </summary>
		/// <param name="url">url</param>
		/// <param name="headers">标准HTTP头</param>
		/// <param name="data">上传数据</param>
		/// <param name="sync">大于零为异步尝试，结果存储在ItemValue中，返回值为键</param>
		/// <returns>url result</returns>
		[SqlFunction(IsDeterministic = false, IsPrecise = false)]
		public static SqlBinary UploadData([Description("资源的HTTP地址")] SqlString url, [Description("标准HTTP头")] SqlString headers, [Description("上传数据")] SqlBinary data, [Description("大于零为异步尝试，结果存储在ItemValue中，返回值为键")] SqlInt32 sync)
		{
			if (url.IsNull || string.IsNullOrEmpty(url.Value))
				return null;

			if (data.IsNull || data.Value == null || data.Value.Length == 0)
				return null;

			if (sync.IsNull || sync.Value <= 0)
			{
			var wc = new WebClient();
			var result = FillHeaders(wc, headers.IsNull ? null : headers.Value).UploadData(url.Value, data.Value);
			wc.Dispose();
			return new SqlBinary(result);
			}

			var t = Guid.NewGuid().ToString("N");
			UploadDataCompleted(new object[] { t, url.Value, headers.IsNull ? null : headers.Value, data.Value, sync.Value, }, null);
			return Encoding.Default.GetBytes(t);
		}

		private static void UploadDataCompleted(object sender, UploadDataCompletedEventArgs e)
		{
			var os = sender as object[];
			try
			{
				var w = SyncCompleted(sender, e, e != null && e.Error == null && e.Result != null ? e.Result : null, ref os);
				if (w == null)
					return;

				w.UploadDataCompleted += UploadDataCompleted;
				w.UploadDataAsync(new Uri(w.BaseAddress), WebRequestMethods.Http.Post, os[3] as byte[], os);
			}
			catch (Exception x)
			{
				try
				{
					if (os != null && os.Length > 0)
						Common.ItemData.Set(os[0] as string, Encoding.Default.GetBytes(os[0] + x.Message), validity: 0L, timeout: DateTime.Now.AddHours(1).Ticks);
					SendUdpText("Sync:\r\n" + os[0] + "\r\n" + os[1], Host, Port);
				}
				catch { }
			}
			sender = null;
		}

		private static WebClient SyncCompleted(object sender, AsyncCompletedEventArgs e, byte[] result, ref object[] state)
		{
			var os = sender as object[];
			if (os == null)
				os = e.UserState as object[];
			if (e != null)
			{
				if (e.Error == null)
				{
					Common.ItemData.Set(os[0] as string, result, validity: 0L, timeout: DateTime.Now.AddHours(1).Ticks);
					try
					{
						SendUdpText("Sync:\r\n" + os[0] + "\r\n" + os[1], Host, Port);
					}
					catch { }
					return null;
				}

				var x = e.Error as WebException;
				if (x != null && x.Response != null)
				{
					try
					{
						var hp = x.Response as HttpWebResponse;
						using (var sr = new StreamReader(hp.GetResponseStream()))
							Common.ItemData.Set(os[0] as string, Encoding.Default.GetBytes(os[0] + hp.StatusCode.ToString() + "\r\n" + hp.StatusDescription + "\r\n" + sr.ReadToEnd() + e.Error.Message), validity: 0L, timeout: DateTime.Now.AddHours(1).Ticks);
					}
					catch// (Exception x)
					{
						Common.ItemData.Set(os[0] as string, Encoding.Default.GetBytes(os[0] + e.Error.Message), validity: 0L, timeout: DateTime.Now.AddHours(1).Ticks);
					}
				}
				else
				{
					Common.ItemData.Set(os[0] as string, Encoding.Default.GetBytes(os[0] + e.Error.Message), validity: 0L, timeout: DateTime.Now.AddHours(1).Ticks);
				}
				if ((int)os[os.Length - 1] <= 0)
				{
					try
					{
						SendUdpText("Sync:\r\n" + os[0] + "\r\n" + os[1], Host, Port);
					}
					catch { }
					return null;
				}

				os[os.Length - 1] = (int)os[os.Length - 1] - 1;
			}
			var w = new WebClient();
			var h = os[2] as string;
			if (!string.IsNullOrEmpty(h))
			{
				try
				{
					FillHeaders(w, h);
				}
				catch (Exception x)
				{
					Common.ItemData.Set(os[0] as string, Encoding.Default.GetBytes(os[0] + x.Message), validity: 0L, timeout: DateTime.Now.AddHours(1).Ticks);
					try
					{
						SendUdpText("Sync:\r\n" + os[0] + "\r\n" + os[1], Host, Port);
					}
					catch { }
					return null;
				}
			}
			w.BaseAddress = os[1] as string;
			state = os;
			return w;
		}

		private static WebClient FillHeaders(WebClient w, string h)
		{
			if (string.IsNullOrEmpty(h))
				return w;

			foreach (var item in h.Split(new char[] { '\r', '\n', }, StringSplitOptions.RemoveEmptyEntries))
			{
				var i = item.IndexOf(':');
				if (i <= 0)
					continue;

				var t = item.Remove(i);
				if (t == "Content-Encoding")
					w.Encoding = Encoding.GetEncoding(item.Substring(i + 1).Trim());
				else
					w.Headers[t] = item.Substring(i + 1).Trim();
			}
			return w;
		}
	}
	/*
	 * 消息 6503，级别 16，状态 12，第 1 行
	 * 在 SQL 目录中找不到程序集 'system.web, version=2.0.0.0, culture=neutral, publickeytoken=b03f5f7f11d50a3a.'。
	 * */
}
